package org.terramagnet.http.util;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
import java.util.Map;

/**
 * Internal static utilities for handling data.
 *
 */
public final class DataUtil {

	private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*(?:\"|')?([^\\s,;\"']*)");
	public static final Pattern pathVariablePattern = Pattern.compile("\\{((?!\\d)[0-9a-zA-Z]+)\\}");
	public static final String defaultCharset = "UTF-8"; // used if not found in header or meta charset
	private static final int bufferSize = 0x20000; // ~130K.
	private static final int UNICODE_BOM = 0xFEFF;
	private static final char[] mimeBoundaryChars
		= "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
	static final int boundaryLength = 32;

	private DataUtil() {
	}

	/**
	 * Writes the input stream to the output stream. Doesn't close them.
	 *
	 * @param in input stream to read from
	 * @param out output stream to write to
	 * @throws IOException on IO error
	 */
	public static void crossStreams(final InputStream in, final OutputStream out) throws IOException {
		final byte[] buffer = new byte[bufferSize];
		int len;
		while ((len = in.read(buffer)) != -1) {
			out.write(buffer, 0, len);
		}
	}

	/**
	 * Read the input stream into a byte buffer.
	 *
	 * @param inStream the input stream to read from
	 * @param maxSize the maximum size in bytes to read from the stream. Set to 0 to be unlimited.
	 * @return the filled byte buffer
	 * @throws IOException if an exception occurs whilst reading from the input stream.
	 */
	public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) throws IOException {
		Validate.isTrue(maxSize >= 0, "maxSize must be 0 (unlimited) or larger");
		final boolean capped = maxSize > 0;
		byte[] buffer = new byte[bufferSize];
		ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize);
		int read;
		int remaining = maxSize;

		while (true) {
			read = inStream.read(buffer);
			if (read == -1) {
				break;
			}
			if (capped) {
				if (read > remaining) {
					outStream.write(buffer, 0, remaining);
					break;
				}
				remaining -= read;
			}
			outStream.write(buffer, 0, read);
		}
		ByteBuffer byteData = ByteBuffer.wrap(outStream.toByteArray());
		return byteData;
	}

	static ByteBuffer readToByteBuffer(InputStream inStream) throws IOException {
		return readToByteBuffer(inStream, 0);
	}

	static ByteBuffer readFileToByteBuffer(File file) throws IOException {
		RandomAccessFile randomAccessFile = null;
		try {
			randomAccessFile = new RandomAccessFile(file, "r");
			byte[] bytes = new byte[(int) randomAccessFile.length()];
			randomAccessFile.readFully(bytes);
			return ByteBuffer.wrap(bytes);
		} finally {
			if (randomAccessFile != null) {
				randomAccessFile.close();
			}
		}
	}

	public static ByteBuffer emptyByteBuffer() {
		return ByteBuffer.allocate(0);
	}

	/**
	 * Parse out a charset from a content type header. If the charset is not supported, returns null (so the default
	 * will kick in.)
	 *
	 * @param contentType e.g. "text/html; charset=EUC-JP"
	 * @return "EUC-JP", or null if not found. Charset is trimmed and uppercased.
	 */
	public static String getCharsetFromContentType(String contentType) {
		if (contentType == null) {
			return null;
		}
		Matcher m = charsetPattern.matcher(contentType);
		if (m.find()) {
			String charset = m.group(1).trim();
			charset = charset.replace("charset=", "");
			if (charset.length() == 0) {
				return null;
			}
			try {
				if (Charset.isSupported(charset)) {
					return charset;
				}
				charset = charset.toUpperCase(Locale.ENGLISH);
				if (Charset.isSupported(charset)) {
					return charset;
				}
			} catch (IllegalCharsetNameException e) {
				// if our advanced charset matching fails.... we just take the default
				return null;
			}
		}
		return null;
	}

	/**
	 * Creates a random string, suitable for use as a mime boundary
	 */
	public static String mimeBoundary() {
		final StringBuilder mime = new StringBuilder(boundaryLength);
		final Random rand = new Random();
		for (int i = 0; i < boundaryLength; i++) {
			mime.append(mimeBoundaryChars[rand.nextInt(mimeBoundaryChars.length)]);
		}
		return mime.toString();
	}

	public static boolean isBlank(String str) {
		int strLen;
		if (str == null || (strLen = str.length()) == 0) {
			return true;
		}
		for (int i = 0; i < strLen; i++) {
			if ((Character.isWhitespace(str.charAt(i)) == false)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 替换路径变量(PathVariable). 不包含变量时原样返回。
	 *
	 * <p>
	 * 未知变量不会被替换。</p>
	 *
	 * @param url 需要替换路径变量的url
	 * @param root 变量值.
	 * @return 替换后的文本
	 */
	public static String replacePathVariables(String url, Map<String, String> root) {
		Matcher matcher = pathVariablePattern.matcher(url);

		int prev = 0;
		StringBuilder text = new StringBuilder();
		while (matcher.find()) {
			String variableName = matcher.group(1);
			String variableValue = root.get(variableName);
			if (variableValue == null) {
				continue;
			}
			text.append(url.substring(prev, matcher.start()))
				.append(variableValue);
			prev = matcher.end();
		}
		if (prev < url.length()) {
			text.append(url.substring(prev));
		}
		return text.toString();
	}

}
